﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using RootfusAddinView;
using System.AddIn;
using ROOTNET.Interface;
using Microsoft.Win32;

namespace RootfusBuiltinAddins.THist1D
{
    /// <summary>
    /// 1D Histogram on the surface, dude!!
    /// </summary>
    [AddIn("1D Histogram(TH1)")]
    public class THist1DAddIn : RootfusSurfaceItem
    {
        private THist1DUINormal _ui = null;

        public THist1DAddIn()
        {
            _input_pins.Add(new THistInputPin());
            _input_pins.Add(new THistObjectPin());
            DontShowTitle = false;
            DontShowStats = false;
            SaveAsFilename = "";
            AutoSaveHistAfterUpdate = false;
            NumberOfBins = 0;
            XAxisTitle = "";
            YAxisTitle = "";
            YMin = "";
            YMax = "";
        }

        /// <summary>
        /// Gets or sets if we are going to show the title.
        /// </summary>
        public bool DontShowTitle { get; set; }

        /// <summary>
        /// Gets or sets if we are going to show our stats.
        /// </summary>
        public bool DontShowStats { get; set; }

        /// <summary>
        /// Should we re-scale these histos?
        /// </summary>
        public bool ScaleIntegralToOne { get; set; }

        /// <summary>
        /// Filename where we will save everything.
        /// </summary>
        public string SaveAsFilename { get; set; }

        /// <summary>
        /// Should we save the histogram everytime we update it? Only if there is a valid file...
        /// </summary>
        public bool AutoSaveHistAfterUpdate { get; set; }

        /// <summary>
        /// Get/set the X axis title. If left blank then we will use whatever is in the histogram.
        /// </summary>
        public string YAxisTitle { get; set; }

        /// <summary>
        /// Get/set the X axis title. If left blank then we will use whatever is in the histogram.
        /// </summary>
        public string XAxisTitle { get; set; }

        /// <summary>
        /// Get/Set the ymax - blank means no setting. Otherwise it is a double number.
        /// </summary>
        public string YMax { get; set; }

        /// <summary>
        /// Get/Set the ymin - blank means no setting. Otherwise it is a double number.
        /// </summary>
        public string YMin { get; set; }

        private int _desired_number_of_bins;
        private int _rebinning_factor;

        /// <summary>
        /// How many bins does the guy want? We have to keep a rebinning factor around...
        /// </summary>
        public int DesiredNumberOfBins
        {
            get { return _desired_number_of_bins; }
            set
            {
                _desired_number_of_bins = value;
                double exact_binning_factor = ((double)_number_of_bins) / ((double)value);
                if (_rebinning_factor != (int)exact_binning_factor)
                {
                    _rebinning_factor = (int)exact_binning_factor;
                    Update();
                }
            }
        }

        /// <summary>
        /// Private stash for the # of bins.
        /// </summary>
        private int _number_of_bins;

        /// <summary>
        /// How many bins are in this histo? And how does it affect our re-binning ability?
        /// </summary>
        public int NumberOfBins
        {
            get
            {
                return _number_of_bins;
            }
            set
            {
                if (_number_of_bins != value)
                {
                    _number_of_bins = value;
                    DesiredNumberOfBins = _number_of_bins;
                }
            }
        }

        /// <summary>
        /// Return the surface item that shows the 1D histogram.
        /// </summary>
        /// <returns></returns>
        public override FrameworkElement SurfaceUI
        {
            get
            {
                if (_ui == null)
                {
                    _ui = new THist1DUINormal();
                }
                return _ui;
            }
        }

        /// <summary>
        /// We don't mind if we change size. We just deal with it. :-)
        /// </summary>
        public override bool SurfaceUIResizeAllowed
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Saves the settings...
        /// </summary>
        public class THist1DSettings
        {
            public bool _dontshowstats = false;
            public bool _dontshowtitle = false;
            public bool _scaleintegraltoone = false;
            public string _save_as_filename = "";
            public bool _auto_save = false;
            public int _desired_number_of_bins = 0;
            public int _number_of_bins = 0; // Not really user setable, but important for our state.
            public string _xaxis_title = "";
            public string _yaxis_title = "";
            public string _ymax = "";
            public string _ymin = "";
        }

        private THist1DSettings _last_saved_settings = new THist1DSettings();

        /// <summary>
        /// Save the settings.
        /// </summary>
        private void CacheSettings()
        {
            _last_saved_settings._dontshowstats = DontShowStats;
            _last_saved_settings._dontshowtitle = DontShowTitle;
            _last_saved_settings._scaleintegraltoone = ScaleIntegralToOne;
            _last_saved_settings._save_as_filename = SaveAsFilename;
            _last_saved_settings._auto_save = AutoSaveHistAfterUpdate;
            _last_saved_settings._desired_number_of_bins = DesiredNumberOfBins;
            _last_saved_settings._number_of_bins = NumberOfBins;
            _last_saved_settings._xaxis_title = XAxisTitle;
            _last_saved_settings._yaxis_title = YAxisTitle;
            _last_saved_settings._ymax = YMax;
            _last_saved_settings._ymin = YMin;
        }

        /// <summary>
        /// Returns true if the cache matches life.
        /// </summary>
        /// <returns></returns>
        private bool CurrentSettingsMatchCache()
        {
            return _last_saved_settings._dontshowtitle == DontShowTitle
                && _last_saved_settings._dontshowstats == DontShowStats
                && _last_saved_settings._scaleintegraltoone == ScaleIntegralToOne
                && _last_saved_settings._save_as_filename == SaveAsFilename
                && _last_saved_settings._auto_save == AutoSaveHistAfterUpdate
                && _last_saved_settings._desired_number_of_bins == DesiredNumberOfBins
                && _last_saved_settings._number_of_bins == NumberOfBins
                && _last_saved_settings._yaxis_title == YAxisTitle
                && _last_saved_settings._xaxis_title == XAxisTitle
                && _last_saved_settings._ymin == YMin
                && _last_saved_settings._ymax == YMax;
        }


        THist1DUIConfig _last_settings = null;
        /// <summary>
        /// Builds the Config UI
        /// </summary>
        private FrameworkElement BuildConfigUI ()
        {
            CacheSettings();
            THist1DUIConfig config = new THist1DUIConfig(this);
            _last_settings = config;
            config.DataContext = this;
            return config;
        }

        /// <summary>
        /// We have no settings UI at the moment, so return null.
        /// </summary>
        public override FrameworkElement ConfigUI
        {
            get { return BuildConfigUI() ; }
        }

        /// <summary>
        /// Did the config. If things changed, we'd better ship off our settings!
        /// </summary>
        public override void ConfigDone()
        {
            if (!CurrentSettingsMatchCache())
            {
                CacheSettings();
                Callback.UpdateSaveObject(_last_saved_settings);
                Update();
            }
        }

        /// <summary>
        /// They have some new setting. We should record it! :-)
        /// </summary>
        /// <param name="xml_config"></param>
        public override void SetConfigXML(string xml_config)
        {
            if (xml_config != null && xml_config.Length != 0)
            {
                _last_saved_settings = Callback.ConvertXMLToSettings<THist1DSettings>(xml_config);
                DontShowStats = _last_saved_settings._dontshowstats;
                DontShowTitle = _last_saved_settings._dontshowtitle;
                ScaleIntegralToOne = _last_saved_settings._scaleintegraltoone;
                SaveAsFilename = _last_saved_settings._save_as_filename;
                NumberOfBins = _last_saved_settings._number_of_bins;
                DesiredNumberOfBins = _last_saved_settings._desired_number_of_bins;
                XAxisTitle = _last_saved_settings._xaxis_title;
                YAxisTitle = _last_saved_settings._yaxis_title;
                YMin = _last_saved_settings._ymin;
                YMax = _last_saved_settings._ymax;

                ///
                /// This should be last. :-)
                /// 

                AutoSaveHistAfterUpdate = _last_saved_settings._auto_save;
            }
        }

        /// <summary>
        /// Return a settings UI, if we have one.
        /// </summary>
        public override bool HasSettingsUI
        {
            get { return true; }
        }
        
        /// <summary>
        /// Dummy to place hold for a while.
        /// </summary>
        private class THistInputPin : PinDefinition
        {
            public override string Name
            {
                get { return "TH1 Histogram"; }
            }

            /// <summary>
            /// We can accept any number of histograms on this input pin.
            /// </summary>
            public override PinConnectionType ConnectionType
            {
                get
                {
                    return PinConnectionType.PinInfinite;
                }
            }
        }
        /// <summary>
        /// Pins that are random objects that should be added to the list of things to be drawn!
        /// </summary>
        private class THistObjectPin : PinDefinition
        {
            public override string Name
            {
                get { return "TObject To Draw"; }
            }

            /// <summary>
            /// We can accept any number of histograms on this input pin.
            /// </summary>
            public override PinConnectionType ConnectionType
            {
                get
                {
                    return PinConnectionType.PinInfinite;
                }
            }
        }

        private List<PinDefinition> _input_pins = new List<PinDefinition>();
        public override IList<PinDefinition> InputPins
        {
            get { return _input_pins; }
        }

        private List<string> _output_pins = new List<string>();
        public override IList<string> OutputPinNames
        {
            get { return _output_pins; }
        }

        public class HistPinReference : PinDefinition
        {
            private string _name;
            private NTH1 _hist;
            public HistPinReference(string name, NTH1 hist)
            {
                _name = name;
                _hist = hist;
            }
            public override string Name
            {
                get { return _name; }
            }
            public NTH1 Hist
            {
                get { return _hist; }
            }
        }
        public Dictionary<string, HistPinReference> _output_cache = new Dictionary<string, HistPinReference>();

        /// <summary>
        /// Return the pin data as a root object. Make sure to make a match
        /// with our current guys... just in case something has changed!
        /// </summary>
        /// <param name="pin"></param>
        /// <returns></returns>
        public override NTObject GetPinDataAsTObject(string pin)
        {
            if (!_output_cache.ContainsKey(pin))
            {
                return null;
            }
            HistPinReference hist = _output_cache[pin];
            return hist.Hist;
        }

        /// <summary>
        /// Ok! Look to see if we have a new histogram!
        /// </summary>
        public override void Update()
        {
            short[] colors = new short[5] { (short) ROOTNET.EColor.kBlack,
                    (short) ROOTNET.EColor.kRed,
                    (short) ROOTNET.EColor.kBlue,
                    (short) ROOTNET.EColor.kGreen,
                    (short) ROOTNET.EColor.kCyan };

            NTObject[] base_obj = Callback.GetConnectedPinDataAsTObjects(InputPins[0]);
            string[] obj_names = Callback.GetNamesOfConnectedPins(InputPins[0]);

            NTH1[] histos = new NTH1[base_obj.Count(x => x != null)];

            _output_pins.Clear();
            int index = 0;
            int full_index = 0;
            double max_value = -10000.0;
            foreach (var item in base_obj)
            {
                NTH1 obj = item.Clone() as NTH1;
                string full_name = obj_names[full_index];
                full_index += 1;
                if (obj == null)
                {
                    break;
                }
                histos[index] = obj;

                ///
                /// Any rebinning that we are planning on doing?
                /// 

                NumberOfBins = obj.GetNbinsX();
                if (_rebinning_factor != 1)
                {
                    obj.Rebin(_rebinning_factor);
                }

                ///
                /// Rescale the histo if asked...
                /// 

                if (ScaleIntegralToOne)
                {
                    obj.Scale(1.0 / obj.Integral());
                }

                ///
                /// Get the max size, and cache the info for later resizing and drawing.
                /// We seem to have to do this funny workaround here because getmaximum isn't working??
                /// 

                int max_bin = obj.GetMaximumBin();
                double hist_max = obj.GetBinContent(max_bin);
                if (hist_max > max_value)
                {
                    max_value = hist_max;
                }

                ///
                /// Turn off the stats if necessary. Otherwise, we are going to go through
                /// and grab them later. This is a rather ugly way to do it, but it seems
                /// to be the way that ROOT works. :(
                /// 

                if (DontShowStats)
                {
                    obj.SetStats(false);
                }

                ///
                /// Do the other simple options -- like showing a title.
                /// 

                if (DontShowTitle)
                {
                    obj.SetBit(ROOTNET.NTH1.kNoTitle, true);
                }

                ///
                /// Do the x and y axis titles
                /// 

                if (XAxisTitle != "")
                {
                    obj.SetXTitle(XAxisTitle);
                }
                if (YAxisTitle != "")
                {
                    obj.SetYTitle(YAxisTitle);
                }

                ///
                /// Set the line color.
                /// 

                obj.SetLineColor(colors[index % colors.Length]);
                obj.SetMarkerColor(colors[index % colors.Length]);
                index += 1;

                ///
                /// Send it on to the next guy!
                /// 

                _output_cache[full_name] = new HistPinReference(full_name, obj);
            }

            ///
            /// Sort out the min and max values. The max value we got above is increased by 10%
            /// to give a little overhead. This (usually) works. But error bars can really mess up
            /// that sort of calculation.
            /// 

            if (YMax != "")
            {
                try
                {
                    double temp = Convert.ToDouble(YMax);
                    max_value = temp;
                }
                catch
                {
                    max_value = max_value * 1.1;
                }
            }
            else
            {
                max_value = max_value * 1.1;
            }

            double min_value = 0.0;
            bool min_value_valid = false;
            if (YMin != "")
            {
                try
                {
                    min_value = Convert.ToDouble(YMin);
                    min_value_valid = true;
                }
                catch
                {

                }
            }

            ///
            /// Now draw them. If we aren't doing stats boxes, pick off the
            /// ones we find and shift them down so they are readable.
            /// 

            if (_ui != null)
            {
                _ui.Draw(null);
                int count = 0;
                double stat_bottom = 0.0;
                foreach (var h in histos)
                {
                    h.SetMaximum(max_value);
                    if (min_value_valid)
                    {
                        h.SetMinimum(min_value);
                    }
                    _ui.DrawSame(h);

                    if (!DontShowStats)
                    {
                        _ui.Update();
                        NTPaveStats stats = _ui.GetPrimative("stats") as NTPaveStats;
                        if (stats != null)
                        {
                            /// Rename so there are no later conflicts!
                            string name = "hist_stats_" + count.ToString();
                            count += 1;
                            stats.SetName(name);
                            short hist_color = h.GetLineColor();
                            stats.SetLineColor(hist_color);
                            stats.SetTextColor(hist_color);

                            if (stat_bottom != 0.0)
                            {
                                double delta = stats.GetY2NDC() - stats.GetY1NDC();
                                stats.SetY1NDC(stat_bottom - delta);
                                stats.SetY2NDC(stat_bottom);
                            }
                            stat_bottom = stats.GetY1NDC() - 0.02;
                        }
                    }
                }
                _ui.Update();
            }

            ///
            /// Finally - if we have any objects that need to be drawn, do them here. These are just done as-is,
            /// and are not passed on down the line.
            /// 

            NTObject[] justobj_obj = Callback.GetConnectedPinDataAsTObjects(InputPins[1]);
            foreach (var item in justobj_obj)
            {
                _ui.DrawSame(item);
                _ui.Update();
            }

            ///
            /// If we should autosave, then we should autosave... :_)
            /// 

            if (AutoSaveHistAfterUpdate && SaveAsFilename != "")
            {
                SaveCanvas();
            }

            ///
            /// Get the output pin names correct!
            /// 

            _output_pins = _output_cache.Keys.ToList();

            ///
            /// Finally, if our inputs changed, so did our outputs!
            /// 

            Callback.OutputPinsChanged();
        }

        /// <summary>
        /// Write the canvas contents out to a file. If no default file is set, ask.
        /// </summary>
        /// <returns></returns>
        internal void Save()
        {
            if (SaveAsFilename == "")
            {
                SaveAs();
            }
            else
            {
                SaveCanvas();
            }
        }

        /// <summary>
        /// Keep this around so if they change, we have the place to start from.
        /// </summary>
        private SaveFileDialog _sd = null;

        /// <summary>
        /// Actually save a file, but ask about it first.
        /// </summary>
        /// <returns></returns>
        internal void SaveAs()
        {
            if (_sd == null)
            {
                _sd = new SaveFileDialog();
                _sd.AddExtension = true;
                _sd.DefaultExt = ".jpg";
                _sd.InitialDirectory = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                _sd.Filter = "JPG Files|*.jpg|EPS Files|*.eps|SVG Files|*.svg|All files|*.*";
            }
            _sd.FileName = SaveAsFilename;
            if ((bool) _sd.ShowDialog())
            {
                SaveAsFilename = _sd.FileName;
                SaveCanvas();
            }
        }

        /// <summary>
        /// Write the file out to our given place...
        /// </summary>
        private void SaveCanvas()
        {
            if (_ui != null)
            {
                _ui.SaveCanvasAsFile(SaveAsFilename);
            }
        }
    }
}
